package m2st.doxia.macro;

import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringEscapeUtils;

/**
 * Code snippet taken from a specific source file denoted by {@link URL}.
 * 
 * <p>
 * 
 * Each snippet represents either a continuous data region
 * taken from the source file (<tt>id</tt> is not <tt>null</tt>)
 * or the entire source file contents (<tt>id</tt> is <tt>null</tt>).
 * 
 * <p>
 * 
 * This class uses regular expressions to identify the data region
 * within the source file according to the given <tt>id</tt>, for
 * example:
 * 
 * <pre>
 * Some content
 * 
 * START SNIPPET: myCode
 * Content for snippet with id 'myCode'
 * END SNIPPET: myCode
 * 
 * Some content
 * </pre>
 * 
 * @see CodeSnippetMacro
 * 
 * @author milan.skuhra
 * @author vojtech.szocs
 */
public class CodeSnippet {

    private static final Pattern START_SNIPPET = Pattern.compile("START SNIPPET:\\s*(\\S+)");
    private static final Pattern END_SNIPPET = Pattern.compile("END SNIPPET:\\s*(\\S+)");
    
    private static final String TAG_NAME = "pre";
    
    private final String content;
    
    private final Map<String, String> attributes = new HashMap<String, String>();
    
    /**
     * Creates a new code snippet.
     * 
     * @param id Snippet identifier (can be <tt>null</tt>
     * to insert the entire source file contents).
     * @param url Source file {@link URL}.
     * @param encoding Source file encoding.
     * @throws IOException
     */
    public CodeSnippet(String id, URL url, String encoding) throws IOException {
        StringBuilder snippet = new StringBuilder();
        LineIterator lines = IOUtils.lineIterator(url.openStream(), encoding);
        
        try {
            boolean include = (id == null);
            
            while (lines.hasNext()) {
                String line = lines.nextLine();
                
                if (match(line, START_SNIPPET, id)) {
                    include = true;
                } else if (match(line, END_SNIPPET, id)) {
                    break;
                } else if (include == true) {
                    snippet.append(line).append(System.getProperty("line.separator"));
                }
            }
            
            content = snippet.toString();
        } finally {
            LineIterator.closeQuietly(lines);
        }
    }
    
    /**
     * Returns <tt>true</tt> when <tt>line</tt> matches
     * the given <tt>pattern</tt> and the first match
     * group is equal to <tt>value</tt>.
     * 
     * <p>
     * 
     * Returns <tt>false</tt> when the <tt>value</tt>
     * is <tt>null</tt>.
     * 
     * @param line Line input to match.
     * @param pattern Pattern to match against.
     * @param value Expected first match group value.
     * @return Input match result.
     */
    private boolean match(String line, Pattern pattern, String value) {
        if (value == null)
            return false;
        
        Matcher m = pattern.matcher(line);
        return m.find() && value.equals(m.group(1));
    }
    
    /**
     * Sets an attribute to be reflected into the final
     * HTML code for this snippet.
     * 
     * @param name Attribute name.
     * @param value Attribute value.
     */
    public void setAttribute(String name, String value) {
        attributes.put(name, value);
    }
    
    /**
     * Wraps the escaped snippet content in a tag with
     * attributes and returns the resulting HTML code.
     * 
     * @return Snippet HTML code.
     */
    @Override
    public String toString() {
        StringBuilder html = new StringBuilder();
        html.append("<").append(TAG_NAME);
        
        Set<String> attributeNames = attributes.keySet();
        for (String name : attributeNames) {
            html.append(" ").append(name).append("=").append("\"").append(attributes.get(name)).append("\"");
        }
        
        html.append(">")
            .append(StringEscapeUtils.escapeHtml(content))
            .append("</").append(TAG_NAME).append(">");
        
        return html.toString();
    }
    
}
